﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using Random = System.Random;

namespace Tetris2020.Scripts.UI
{
    public class TetrisBoard : MonoBehaviour
    {
        [Tooltip("当前正在下落的方块. ")] [SerializeField]
        public TetrisBox current;

        [Tooltip("俄罗斯方块游戏面板的宽度. 单位：块")] [SerializeField] [Range(10, 14)]
        public int width;

        [Tooltip("俄罗斯方块游戏面板的高度. 单位：块")] [SerializeField] [Range(16, 22)]
        public int height;

        [Tooltip("方块的初始点. 方块产生并开始下落的点")] [SerializeField]
        public Vector2 birthPoint;

        [Tooltip("方块自动下落的时间间隔. 单位：毫秒. 实际时间间隔将根据等级逐渐减小. 缺省值为：500")] [SerializeField] [Range(1, 1000000)]
        public int autoDropInterval;

        [Tooltip("方块加速移动的时间间隔. 单位：毫秒. 缺省值为：50")] [SerializeField] [Range(1, 1000000)]
        public int fastMoveInterval;

        [Tooltip("游戏面板. 保存和显示当前的游戏状态")] [SerializeField]
        public Transform content;

        [Tooltip("单个正方形的Prefab资源. 用于拼装和显示固定的方块")] [SerializeField]
        public GameObject prefabSingleRect;

        [Tooltip("本局游戏积分信息面板")] [SerializeField]
        public TetrisGoalPanel tetrisGoalPanel;

        [Tooltip("游戏结束确认面板. 点击确认跳转到菜单界面.")] [SerializeField]
        public GameObject gameEndConfirmPanel;

        [Tooltip("俄罗斯方块资源列表. Prefab资源. 用于生成游戏中的俄罗斯方块")] [SerializeField]
        public GameObject[] prefabTetrisBoxArray;

        [Tooltip("消除方块的行数所对应的奖励分数")] [SerializeField]
        public TetrisGoal[] rowCountGoalDict;

        /// <summary>
        /// 移动完成
        /// </summary>
        public event EventHandler OnMoveCompleted;

        /// <summary>
        /// 游戏结束
        /// </summary>
        public event EventHandler OnGameOver;

        /// <summary>
        /// 阻挡 - 地图阻挡信息
        /// </summary>
        public GameObject[,] BoardGrid { get; set; }

        /// <summary>
        /// 俄罗斯方块的队列. 
        /// </summary>
        private readonly Queue<GameObject> _mTetrisBoxQueue = new Queue<GameObject>();

        /// <summary>
        /// Random工具. 用于生成随机数.
        /// </summary>
        private readonly Random _mRandom = new Random();

        /// <summary>
        /// 是否游戏结束.
        /// </summary>
        private bool _isGameOver;

        /// <summary>
        /// 最后一次左移或者右移
        /// </summary>
        private long _timeLastLeftOrRight;

        /// <summary>
        /// 最后一次下降
        /// </summary>
        private long _timeLastAutoDown;

        /// <summary>
        /// 是否快速下降
        /// </summary>
        public bool IsFastDown { get; set; }

        /// <summary>
        /// 快速左移
        /// </summary>
        public bool IsFastLeft { get; set; }

        /// <summary>
        /// 快速右移
        /// </summary>
        public bool IsFastRight { get; set; }

        private void Awake()
        {
            this.BoardGrid = new GameObject[this.width, this.height];
            this.OnMoveCompleted += _tetrisBoxMoveCompleted;
            this.OnGameOver += _tetrisGameOver;
        }

        private void Start()
        {
            this._isGameOver = false;
            //    clear all content
            for (var i = 0; i < content.childCount; i++)
            {
                Destroy(content.GetChild(i).gameObject);
            }

            if (this.gameEndConfirmPanel != null)
            {
                this.gameEndConfirmPanel.SetActive(false);
            }

            //    reset game score.
            this.tetrisGoalPanel.Score = 0;
            this.tetrisGoalPanel.Level = 0;

            this.ShowNextTetrisBox();
        }

        private void Update()
        {
            if (this._isGameOver)
                return;

            var box = this.current;
            if (null == box)
            {
                this.GenerateNextTetrisBox();
            }
            else
            {
                #region 键盘控制

                // 键盘控制
                if (Input.GetKeyDown(KeyCode.UpArrow))
                    this.OnRotateBox();
                else if (Input.GetKey(KeyCode.LeftArrow))
                    this.OnTurnLeft();
                else if (Input.GetKey(KeyCode.RightArrow))
                    this.OnTurnRight();
                else if (Input.GetKey(KeyCode.DownArrow))
                {
                    this.IsFastDown = true;
                    this.OnTurnDown();
                }

                if (Input.GetKeyUp(KeyCode.DownArrow))
                {
                    this.IsFastDown = false;
                }

                #endregion

                this.OnTurnDown();
            }
        }

        /// <summary>
        /// 旋转方块, 变换方块的形态
        /// </summary>
        public void OnRotateBox()
        {
            var that = this.current;
            Debug.Assert(that != null, "Current Tetris Box");
            var position = that.CurrentPosition.Select(vec => that.TryPreviewRotateBox(vec)).ToArray();
            if (position.Length <= 0)
                return;

            if (IsCanChange(position))
                that.OnRotateBox(position);
        }

        /// <summary>
        /// 左移
        /// </summary>
        public void OnTurnLeft()
        {
            var that = this.current;
            Debug.Assert(default(TetrisBox) != that, "Current Tetris Box");
            var now = DateTime.Now.Ticks;
            if (now - _timeLastLeftOrRight <= this.fastMoveInterval * 10000) return;
            _timeLastLeftOrRight = now;
            var position = that.CurrentPosition.Select(vec => vec - Vector2.right).ToArray();
            if (IsCanChange(position))
            {
                that.OnTurnLeft(position);
            }
        }

        /// <summary>
        /// 右移
        /// </summary>
        public void OnTurnRight()
        {
            var that = this.current;
            Debug.Assert(that != default(TetrisBox), "Current Tetris Box");
            var now = DateTime.Now.Ticks;
            if (now - _timeLastLeftOrRight <= this.fastMoveInterval * 10000) return;
            _timeLastLeftOrRight = now;
            var position = that.CurrentPosition.Select(vec => vec + Vector2.right).ToArray();
            if (IsCanChange(position))
            {
                that.OnTurnRight(position);
            }
        }

        /// <summary>
        /// 下落
        /// </summary>
        public void OnTurnDown()
        {
            var that = this.current;
            Debug.Assert(that != default(TetrisBox), "Current Tetris Box");
            //    auto drop box.
            var now = DateTime.Now.Ticks;
            if ((now - _timeLastAutoDown <= this.autoDropInterval * 10000) &&
                (!IsFastDown || now - _timeLastAutoDown <= this.fastMoveInterval * 10000)) return;
            _timeLastAutoDown = now;
            var position = that.CurrentPosition.Select(vec => vec - Vector2.up).ToArray();
            if (this.OnValidateAllPositionPoint(position))
            {
                that.OnTurnDown(position);
            }
            else
            {
                this.OnValidateGameOver(that.CurrentPosition);
            }
        }

        /// <summary>
        /// 游戏结束事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _tetrisGameOver(object sender, EventArgs e)
        {
            Debug.Log("Game Over");
            if (gameEndConfirmPanel == null)
            {
                this.OnConfirmGameOver();
            }
            else
            {
                this.gameEndConfirmPanel.SetActive(true);
            }
        }

        public void OnConfirmGameOver()
        {
            this._isGameOver = true;
            SceneManager.LoadScene("Menu");
        }

        /// <summary>
        /// 移动结束时间
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _tetrisBoxMoveCompleted(object sender, EventArgs e)
        {
            var args = e as TetrisBoxMoveCompletedEventArgs;
            if (args != null
                && null != args.TetrisBox
                && !this.IsOutScreen(args.TetrisBox.CurrentPosition)
            )
            {
                //    生成固定点.
                foreach (var vec in args.TetrisBox.CurrentPosition)
                {
                    var goRect = Instantiate(this.prefabSingleRect, this.content, false) as GameObject;
                    ((RectTransform) goRect.transform).anchoredPosition =
                        this.OnCovertToUiPosition((int) vec.x, (int) vec.y);
                    this.BoardGrid[(int) vec.x, (int) vec.y] = goRect;
                }

                //    计算游戏得分
                this.OnAfterMoveCompleted(args.TetrisBox);

                //    destroy the un-movable tetris box.
                Destroy(args.TetrisBox.gameObject);

                // 生成下一个
                this.GenerateNextTetrisBox();
            }
        }

        private void GenerateNextTetrisBox()
        {
            var goTetrisBox = GetNextTetrisBox();
            if (goTetrisBox == null)
                throw new Exception("system error. tetris box is null.");
            goTetrisBox.SetActive(true);
            this.current = goTetrisBox.GetComponent<TetrisBox>();
        }

        /// <summary>
        /// 检查并得分
        /// </summary>
        /// <param name="tetrisBox">最后一个操作的方块</param>
        /// <returns>返回新的阻挡信息</returns>
        private void OnAfterMoveCompleted(TetrisBox tetrisBox)
        {
            var dict = new Dictionary<int, bool>();
            var row = this.BoardGrid.GetLength(1);
            var column = this.BoardGrid.GetLength(0);
            foreach (var vec in tetrisBox.CurrentPosition)
            {
                var checkRow = (int) vec.y;
                if (dict.ContainsKey(checkRow))
                    continue;
                //    本行是否可以消除
                var removable = true;
                for (var x = 0; x < column; x++)
                    removable &= this.BoardGrid[x, checkRow] != null;
                dict[checkRow] = removable;
            }

            // 重新设置阻挡信息
            var newAll = new GameObject[column, row];
            var newY = 0;
            for (var y = 0; y < row; y++)
            {
                if (dict.ContainsKey(y))
                {
                    if (dict[y])
                    {
                        //    消除整行的方块
                        for (var x = 0; x < column; x++)
                        {
                            Destroy(this.BoardGrid[x, y]);
                        }

                        continue;
                    }

                    dict.Remove(y);
                }

                for (var x = 0; x < column; x++)
                {
                    var goRect = this.BoardGrid[x, y];
                    newAll[x, newY] = goRect;
                    if (goRect != null)
                    {
                        ((RectTransform) goRect.transform).anchoredPosition = this.OnCovertToUiPosition(x, newY);
                    }
                }

                newY++;
            }

            if (dict.Count > 0)
            {
                this.BoardGrid = newAll;
                this.tetrisGoalPanel.Score += this.rowCountGoalDict[dict.Count - 1].score;
            }
        }

        private Vector3 OnCovertToUiPosition(int ox, int oy)
        {
            var x = ox * TetrisBox.BoxRectWeight;
            var y = oy * TetrisBox.BoxRectWeight;
            return new Vector3(x, y, 0);
        }

        /// <summary>
        /// 是否存在点是否超出屏幕
        /// </summary>
        /// <param name="positions">点</param>
        /// <returns>返回是否存在点是否超出屏幕</returns>
        public bool IsOutScreen(Vector2[] positions)
        {
            var row = this.BoardGrid.GetLength(1);
            var column = this.BoardGrid.GetLength(0);
            foreach (var vector2 in positions)
                if (vector2.x < 0 || vector2.y < 0 || vector2.x >= column || vector2.y >= row)
                    return true;
            return false;
        }


        /// <summary>
        /// 检查是否游戏结束
        /// 
        /// <br/>
        /// 当不能移动时,原始方块的点超出边界则说明游戏结束
        /// </summary>
        /// <param name="position">点位置</param>
        /// <returns>是否游戏结束</returns>
        private bool OnValidateGameOver(Vector2[] position)
        {
            var row = this.BoardGrid.GetLength(1);
            var column = this.BoardGrid.GetLength(0);
            foreach (var vec in position)
            {
                if (vec.x < 0 || vec.y < 0 || vec.x >= column || vec.y >= row)
                {
                    if (OnGameOver != null) OnGameOver(this, EventArgs.Empty);
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// 方块是否可以下落
        /// </summary>
        /// <param name="tp">方块的点集合</param>
        /// <returns>返回方块是否可以下落</returns>
        private bool OnValidateAllPositionPoint(IEnumerable<Vector2> tp)
        {
            var row = this.BoardGrid.GetLength(1);
            foreach (var vec in tp)
            {
                if (vec.y < 0
                    || (vec.y < row && this.BoardGrid[(int) vec.x, (int) vec.y] != null))
                {
                    if (OnMoveCompleted != null) OnMoveCompleted(this, new TetrisBoxMoveCompletedEventArgs(current));
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 方块是否可以变形
        /// </summary>
        /// <param name="positions">目标形状</param>
        /// <returns>方块是否可以变形</returns>
        private bool IsCanChange(IEnumerable<Vector2> positions)
        {
            var row = this.BoardGrid.GetLength(1);
            var column = this.BoardGrid.GetLength(0);
            foreach (var position in positions)
            {
                if (position.x < 0 || position.x >= column || position.y < 0)
                    return false;
                if ((position.y < row && this.BoardGrid[(int) position.x, (int) position.y] != null))
                    return false;
            }

            return true;
        }

        public GameObject GetNextTetrisBox()
        {
            this.ShowNextTetrisBox();
            return _mTetrisBoxQueue.Dequeue();
        }

        public void ShowNextTetrisBox()
        {
            if (_mTetrisBoxQueue.Count <= 1)
            {
                this.GenerateNewTetrisBoxBag();
            }

            this.tetrisGoalPanel.ShowNextTetrisBox();
        }

        /// <summary>
        /// 随机方块包生成
        /// </summary>
        /// <returns>返回方块包</returns>
        private void GenerateNewTetrisBoxBag()
        {
            var results = new List<KeyValuePair<GameObject, GameObject>>();
            foreach (var prefabTetrisBox in prefabTetrisBoxArray)
            {
                var instance = Instantiate(prefabTetrisBox, this.content, false);
                instance.SetActive(false);
                var tetrisBox = instance.GetComponent<TetrisBox>();
                if (tetrisBox != null)
                {
                    //    init the tetris box's origin point
                    var point = new Vector2((int) (width / 2), height);
                    var state = _mRandom.Next(tetrisBox.rotates.Length);
                    tetrisBox.InitTetrisBox(point, state);
                }

                //    preview
                var previewInstance =
                    Instantiate(prefabTetrisBox, this.tetrisGoalPanel.previewTetrisBoxPanel.transform, false);
                previewInstance.SetActive(false);
                ((RectTransform) previewInstance.transform).anchoredPosition = new Vector3(80, 80, 0);

                results.Add(new KeyValuePair<GameObject, GameObject>(instance, previewInstance));
            }

            foreach (var pair in results.OrderBy(x => _mRandom.Next()).ToList())
            {
                this._mTetrisBoxQueue.Enqueue(pair.Key);
                this.tetrisGoalPanel.Offer(pair.Value);
            }
        }
    }

    [Serializable]
    public class TetrisGoal
    {
        /// <summary>
        /// 消除方块的行数
        /// </summary>
        public int line;

        /// <summary>
        /// 对应的奖励分数
        /// </summary>
        public int score;
    }
}